﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;
using Infraestruturex.Automata.SPA;
using Infraestruturex.BaseLibrary;
using System.Diagnostics;
using System.Runtime.Serialization;
using System.Runtime.Serialization.Formatters.Binary;
using Compilex;

namespace Metex
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                if (args.Length == 0)
                {
                    PrintUsage();
                    return;
                }
                else if (args.Length == 1)
                {
                    string rules = null;
                    try
                    {
                        rules = File.ReadAllText(args[0]);
                    }
                    catch (Exception e)
                    {
                        Console.WriteLine("Erro ao ler arquivo da gramatica: " + e.Message);
                        return;
                    }
                    SyntaticAnalyzer analyzer = MakeSyntaticAnalyzer(
                        System.IO.Path.GetFileNameWithoutExtension(args[0]), rules);
                    DumpSyntaticAnalyzer(analyzer);
                }
                else if(args[0] == "-g")
                {
                    SyntaticAnalyzer analyzer = RetrieveSyntaticAnalyzer(args[1]);
                    DumpSyntaticAnalyzer(analyzer);
                }
                else
                {
                    PrintUsage();
                }
            }
            catch (Exception e)
            {
                Console.WriteLine("Erro: " + e.Message);
            }
        }

        private static SyntaticAnalyzer RetrieveSyntaticAnalyzer(string serializedFile)
        {
            IFormatter formatter = new BinaryFormatter();
            using (Stream stream = new FileStream(serializedFile, FileMode.Open))
            {
                return (SyntaticAnalyzer)formatter.Deserialize(stream);
            }
        }

        private static SyntaticAnalyzer MakeSyntaticAnalyzer(string languageName, string rules)
        {
            Metex metex = new Metex(rules);
            metex.MetaCompile();
            metex.SyntaticAnalyzer.LanguageName = languageName;

            SerializeSyntaticAnalyzer(metex.SyntaticAnalyzer);
            return metex.SyntaticAnalyzer;
        }

        private static void SerializeSyntaticAnalyzer(SyntaticAnalyzer syntaticAnalyzer)
        {
            IFormatter formatter = new BinaryFormatter();
            using (Stream stream = new FileStream(syntaticAnalyzer.LanguageName + ".ex", FileMode.Create))
            {
                formatter.Serialize(stream, syntaticAnalyzer);
            }
        }

        private static void DumpSyntaticAnalyzer(SyntaticAnalyzer syntaticAnalyzer)
        {
            try
            {
                if (Directory.Exists(syntaticAnalyzer.LanguageName))
                {
                    Directory.Delete(syntaticAnalyzer.LanguageName, true);
                }
                Directory.CreateDirectory(syntaticAnalyzer.LanguageName);
            }
            catch (Exception e)
            {
                Console.WriteLine("Nao foi possivel criar diretorio " + syntaticAnalyzer.LanguageName + ": " + e.Message);
                return;
            }
            foreach (var kvp in syntaticAnalyzer.Submachines)
            {
                string gvfile = kvp.Key + ".gv";
                File.WriteAllText(gvfile, DumpToGraphviz(kvp.Value));
                Process p = Process.Start("..\\graphviz\\bin\\dot.exe",
                    " -Teps -Kdot -O " + gvfile);
                p.WaitForExit();
                p = Process.Start("..\\epstopdf.exe", gvfile + ".eps");
                p.WaitForExit();

                File.Delete(gvfile);
                File.Delete(gvfile + ".eps");
                File.Move(gvfile + ".pdf", syntaticAnalyzer.LanguageName + "\\" + gvfile + ".pdf");
            }
        }


        private static void PrintUsage()
        {
            Console.WriteLine("metex.exe arquivo_gramatica: gera o reconhecedor e os grafos");
            Console.WriteLine("metex.exe -g arquivo_reconhecedor: gera os grafos para um reconhecedor ja criado");
        }

        private static string GetRulesFile(string[] args)
        {
            string rules = null;
            string rulesFile = null;
            if (args.Length > 0)
            {
                rulesFile = args[0];
            }

            do
            {
                if (rulesFile == null)
                {
                    Console.WriteLine("Digite o nome do arquivo com a gramática da linguagem: ");
                    rulesFile = Console.ReadLine();
                }

                try
                {
                    rules = File.ReadAllText(rulesFile);
                }
                catch (Exception e)
                {
                    Console.WriteLine("O arquivo " + rulesFile + " nao pode ser aberto: " + e.Message);
                    rulesFile = null;
                }
            } while (rules == null);
            return rules;
        }

        private static string DumpToGraphviz(SPA spa)
        {
            //digraph finite_state_machine {
            //    rankdir=LR;
            //    size="8,5"
            //    node [shape = doublecircle]; 6;
            //    node [shape = circle];
            //    0 -> 1 [ label = "\"id\"" ];
            //    1 -> 1 [ label = "\". id\"" ];
            //    1 -> 2 [ label = "\"=\"" ];
            //}
            const string submachineCallShape = "diamond";
            const string submachineCallColor = "lightblue3";
            const string submachineCallEdgeColor = "red";
            const string normalEdgeColor = "\"0.650 0.200 1.000\"";
            const string finalColor = "chartreuse";
            const string normalColor = @"""0.650 0.200 1.000""";

            StringBuilder blder = new StringBuilder();
            blder.AppendLine("digraph finite_state_machine {");
            blder.AppendLine(@"graph [	fontname = ""Helvetica-Oblique"",
		fontsize = 36,
		label = ""\n\n\n" + spa.Name + @" (inicial: " + spa.StartState.Name + ")\"]");
            blder.AppendLine("  rankdir=LR;");
            blder.AppendLine(@"node [style=filled,fontname = ""Helvetica-Outline""]");


            SPAState[] states = spa.GetStates();
            SPAState[] finalStates = states.Where(s => s.IsAcceptState).ToArray();
            SPAState[] submachineStates = states.Where(s => s.IsSubmachineCallState).ToArray();

            blder.Append(String.Format("  node[color={0}]; ", finalColor));
            finalStates.ForEach(s => blder.Append(s.Name + " "));
            blder.AppendLine(";");

            blder.Append(String.Format("  node[shape={0},color={1}]; ",
                submachineCallShape, submachineCallColor));
            submachineStates.ForEach(s => blder.Append(s.Name + " "));
            blder.AppendLine(";");



            blder.AppendLine(String.Format("    node[shape=circle,color={0}]", normalColor));

            foreach (SPAState state in states)
            {
                if (state.IsSubmachineCallState)
                {
                    blder.AppendLine(
                        String.Format(@" {0} -> {1} [color = {4}, label = ""_${2}$_/{3}"" ];",
                        state.Name, state.SubmachineCallReturnState.Name,
                        state.SubmachineToCall.Name,
                        state.SubmachineCallSemanticActionName,
                        submachineCallEdgeColor
                        ));
                }
                foreach (var transition in state.Transitions.ToArray())
                {
                    blder.AppendLine(
                        String.Format(@" {0} -> {1} [color = {4}, label = ""\""{2}\""/{3}"" ];",
                        state.Name, transition.Destination.Name,
                        transition.Input,
                        transition.SemanticActionName,
                        normalEdgeColor
                        ));
                }
            }

            blder.AppendLine("}");
            return blder.ToString();
        }
    }
}
